Skip to content

Add explicit pin-map support for K64F and NUCLEO_F429ZI #11399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

mprse
Copy link
Contributor

@mprse mprse commented Sep 3, 2019

Description

This PR adds explicit pin-map support for the K64F and NUCLEO_F429ZI for the all common peripherals like:

  • PWM
  • AnalogIn
  • AnalogOut
  • SPI
  • I2C
  • Serial

Tables below show the ROM savings when the traditional pin-map mechanism is used to initialize the peripheral vs explicit pin-map mechanism is used:

image

image

image

In almost, all cases flash memory savings are consistent with the size of the pin-maps used by the peripheral. Additional savings can be also achieved by reducing the size of pin-map lib
(mbed_pinmap_common.o). To get the max savings we need to completely remove the pin-maps, which means explicit pin-map must be used for serial console. In the above tables, serial row includes also extra savings from removing pin-map lib completely(*), by initializing the console using explicit pin-map mechanism.
Unfortunately in one case GCC_ARM/NUCLEO_F429ZI the results are very strange because there is no reduced memory (marked as red). I checked that pin-map tables were removed from elf, but this has no influence on the used memory. Does anyone maybe know why? Looks like it works for GCC_AMR/K64F.

Below you can find detailed information how memory usage changes when example PWM application is build using traditional pin-maps (master) and when the application uses explicit pin-maps (explicit_pinmap_gold):

ARM/NUCLEO_F429ZI
| Module                          |       .text |   .data |      .bss |
|---------------------------------|-------------|---------|-----------|
| [lib]\c_w.l                     |   10959(+0) |  16(+0) |   348(+0) |
| [lib]\fz_wm.l                   |    1322(+0) |   0(+0) |     0(+0) |
| [lib]\m_wm.l                    |      48(+0) |   0(+0) |     0(+0) |
| anon$$obj.o                     |      32(+0) |   0(+0) |  1280(+0) |
| components\802.15.4_RF          |      74(+0) |   0(+0) |     0(+0) |
| components\wifi                 |      60(+0) |   0(+0) |     0(+0) |
| drivers\source                  |     452(+0) |   0(+0) |     0(+0) |
| events\source                   |    1708(+0) |   0(+0) |  3108(+0) |
| features\netsocket              |    2150(+0) |   0(+0) | 12688(+0) |
| hal\mbed_critical_section_api.o |     154(+0) |   0(+0) |     2(+0) |
| hal\mbed_gpio.o                 |      62(+0) |   0(+0) |     0(+0) |
| hal\mbed_pinmap_common.o        |   214(-186) |   0(+0) |     0(+0) |
| hal\mbed_ticker_api.o           |     970(+0) |   0(+0) |     0(+0) |
| hal\mbed_us_ticker_api.o        |      80(+0) |   4(+0) |    64(+0) |
| hal\mpu                         |     164(+0) |   0(+0) |     0(+0) |
| main.o                          |     70(+32) |   0(+0) |     0(+0) |
| platform\source                 |    5736(+0) |  64(+0) |   249(+0) |
| rtos\source                     |   12445(+0) | 168(+0) |  6634(+0) |
| targets\TARGET_STM              | 13606(-788) |   5(+0) |   740(+0) |
| Subtotals                       | 50306(-942) | 257(+0) | 25113(+0) |
Total Static RAM memory (data + bss): 25370(+0) bytes
Total Flash memory (text + data): 50563(-942) bytes

ARM/K64F
| Module                          |       .text |   .data |       .bss |
|---------------------------------|-------------|---------|------------|
| [lib]\c_w.l                     |   11175(+0) |  16(+0) |    348(+0) |
| [lib]\fz_wm.l                   |      34(+0) |   0(+0) |      0(+0) |
| [lib]\m_wm.l                    |      48(+0) |   0(+0) |      0(+0) |
| anon$$obj.o                     |      32(+0) |   0(+0) | 197888(+0) |
| drivers\source                  |     192(+0) |   0(+0) |      0(+0) |
| features\netsocket              |     143(+0) |   0(+0) |      0(+0) |
| hal\mbed_critical_section_api.o |     154(+0) |   0(+0) |      2(+0) |
| hal\mbed_gpio.o                 |      96(+0) |   0(+0) |      0(+0) |
| hal\mbed_pinmap_common.o        |    202(-70) |   0(+0) |      0(+0) |
| hal\mbed_ticker_api.o           |     978(+0) |   0(+0) |      0(+0) |
| hal\mbed_us_ticker_api.o        |     114(+0) |   4(+0) |     65(+0) |
| main.o                          |     70(+32) |   0(+0) |      0(+0) |
| platform\source                 |    5637(+0) |  64(+0) |    249(+0) |
| rtos\source                     |    8990(+0) | 168(+0) |   6626(+0) |
| targets\TARGET_Freescale        | 16971(-426) |  12(+0) |    340(+0) |
| Subtotals                       | 44836(-464) | 264(+0) | 205518(+0) |
Total Static RAM memory (data + bss): 205782(+0) bytes
Total Flash memory (text + data): 45100(-464) bytes

GCC_ARM/NUCLEO_F429ZI  <----- NO SAVINGS FROM PINMAPS ?
| Module                          |      .text |    .data |      .bss |
|---------------------------------|------------|----------|-----------|
| [fill]                          |    90(-40) |    7(+0) |    27(+0) |
| [lib]\c.a                       |  24260(+0) | 2472(+0) |    89(+0) |
| [lib]\gcc.a                     |   3232(+0) |    0(+0) |     0(+0) |
| [lib]\misc                      |    224(+0) |    4(+0) |    28(+0) |
| components\802.15.4_RF          |     16(+0) |    0(+0) |     0(+0) |
| components\wifi                 |     78(+0) |    0(+0) |     0(+0) |
| drivers\source                  |    310(+0) |    0(+0) |     0(+0) |
| events\source                   |   1438(+0) |    0(+0) |  3108(+0) |
| features\lorawan                |     16(+0) |    0(+0) |     0(+0) |
| features\netsocket              |   1882(+0) |    0(+0) | 12688(+0) |
| hal\mbed_critical_section_api.o |     88(+0) |    0(+0) |     2(+0) |
| hal\mbed_gpio.o                 |     78(+0) |    0(+0) |     0(+0) |
| hal\mbed_pinmap_common.o        |  140(-116) |    0(+0) |     0(+0) |
| hal\mbed_ticker_api.o           |    930(+0) |    0(+0) |     0(+0) |
| hal\mbed_us_ticker_api.o        |     30(+0) |    4(+0) |    64(+0) |
| hal\mpu                         |    124(+0) |    0(+0) |     0(+0) |
| main.o                          |    52(+12) |    0(+0) |     0(+0) |
| platform\source                 |   3256(+0) |  260(+0) |   161(+0) |
| rtos\source                     |  10212(+0) |  168(+0) |  5981(+0) |
| targets\TARGET_STM              | 11744(+80) |    5(+0) |   740(+0) |
| Subtotals                       | 58200(-64) | 2920(+0) | 22888(+0) |
Total Static RAM memory (data + bss): 25808(+0) bytes
Total Flash memory (text + data): 61120(-64) bytes

Sizes of the following modules have changed as follows (except GCC_ARM/NUCLEO_F429Z no savings from removed pin-map tables):

  • targets\TARGET_xxxxx (removed pin-map tables, driver modifications),
  • hal\mbed_pinmap_common.o (reduced usage of functions from pin-map lib),
  • main.o (application requires extra space for explicit pin-map structure).

Example usage:

int main()
{
    /* Regular use (master) */
    SPI spi(D1, D2, D3, D4);

    /* Explicit pinmap */
    const spi_pinmap_t explicit_spi_pinmap = {SPI_1, D1, 2, D2, 2, D3, 2, D4, 2};
    SPI spi(explicit_spi_pinmap);

    /* Explicit pinmap with constexpr  - might be added in future */
    constexpr spi_pinmap_t explicit_spi_pinmap = get_spi_pinmap_t(D1, D2, D3, D4);
    SPI spi(explicit_spi_pinmap);

    return 0;
}

If the user tries to use the explicit pin-map mechanism on unsupported target, then will get the failure message:
Error Message: Explicit pinmap for SPI is unsupported on this device.

Pull request type

[ ] Fix
[X] Refactor
[ ] Target update
[ ] Functionality change
[ ] Docs update
[ ] Test update
[ ] Breaking change

Reviewers

@jamesbeyond @kjbracey-arm @0xc0170
@maciejbocianski @fkjagodzinski

@ciarmcom
Copy link
Member

ciarmcom commented Sep 3, 2019

@mprse, thank you for your changes.
@0xc0170 @jamesbeyond @fkjagodzinski @kjbracey-arm @maciejbocianski @ARMmbed/mbed-os-maintainers please review.

@kjbracey
Copy link
Contributor

kjbracey commented Sep 3, 2019

Nice work. Initial comments before detailed review...

Before finalising this, I would like confirmation that the proposed constexpr thing works in principle. I don't want us to have to redesign it later.

Make at least one test constexpr spi_pinmap_t get_spi_pinmap(PinName...) for one target and confirm that it compiles and works with no code size increase versus the manual explicit pinmap. (Actually, should produce an identical binary, I think).

(You can knock it together standalone outside the HAL for now in a test app, duplicating whatever tables are necessary).

For the lack of saving on that target - can you attach the respective before/after map files here, or stick them up somewhere?

@mprse
Copy link
Contributor Author

mprse commented Sep 3, 2019

Make at least one test constexpr spi_pinmap_t get_spi_pinmap(PinName...) for one target and confirm that it compiles and works with no code size increase versus the manual explicit pinmap. (Actually, should produce an identical binary, I think).

I will do that. Unfortunately, I noticed that I missed adding explicit pinmap support for serial flow control. I will add this missing part and then I'll try to use constexpr specifier to search for mappings.

For the lack of saving on that target - can you attach the respective before/after map files here, or stick them up somewhere?

This archive contains map files, elf files, and output from readelf for regular build where spi tables should be included and explicit pinmap build which should be without spi tables.

nucleo_f429zi_gcc.zip

@kjbracey
Copy link
Contributor

kjbracey commented Sep 3, 2019

The image is 968 bytes smaller in those map files - the base one has text size 100f8 (65 784) and the exp one has text size fd30 (64 816).

If that isn't showing up as a 968-byte difference, something in the STM layout must be confusing your memory analysis tool.

I think.

@40Grit
Copy link

40Grit commented Sep 4, 2019

@trowbridgec @AGlass0fMilk check this out

@mprse mprse force-pushed the explicit_pinmap_gold branch from 6d48c21 to d94c076 Compare September 4, 2019 09:45
@mprse
Copy link
Contributor Author

mprse commented Sep 4, 2019

Added missing support for serial flow control.

@0xc0170
Copy link
Contributor

0xc0170 commented Sep 6, 2019

Please check Travis failures

@mprse
Copy link
Contributor Author

mprse commented Sep 10, 2019

Added constexpr utility functions. This functions can be used to find pin mappings in compile time. This feature is available only for K64F at the moment and can be used with all peripherals.

Below memory usage analysis. The results are slightly different than the previous since:

  • I use valid pin mappings
  • For all examples build on mprse:explicit_pinmap_gold branch, the console is using the explicit pin-map mechanism (pin-map lib can be removed completely).

image

image

image

Example memory usage change for ARM/PWM example: master vs constexpr explicit pinmap:

| Module                          |        .text |   .data |       .bss |
|---------------------------------|--------------|---------|------------|
| [lib]\c_w.l                     |    11175(+0) |  16(+0) |    348(+0) |
| [lib]\fz_wm.l                   |       34(+0) |   0(+0) |      0(+0) |
| [lib]\m_wm.l                    |       48(+0) |   0(+0) |      0(+0) |
| anon$$obj.o                     |       32(+0) |   0(+0) | 197888(+0) |
| drivers\source                  |      192(+0) |   0(+0) |      0(+0) |
| features\netsocket              |      143(+0) |   0(+0) |      0(+0) |
| hal\mbed_critical_section_api.o |      154(+0) |   0(+0) |      2(+0) |
| hal\mbed_gpio.o                 |       96(+0) |   0(+0) |      0(+0) |
| hal\mbed_pinmap_common.o        |      0(-272) |   0(+0) |      0(+0) |
| hal\mbed_ticker_api.o           |      978(+0) |   0(+0) |      0(+0) |
| hal\mbed_us_ticker_api.o        |      114(+0) |   4(+0) |     65(+0) |
| main.o                          |      70(+32) |   0(+0) |      0(+0) |
| platform\source                 |    5683(+46) |  64(+0) |    249(+0) |
| rtos\source                     |     8990(+0) | 168(+0) |   6626(+0) |
| targets\TARGET_Freescale        |  16581(-816) |  12(+0) |    340(+0) |
| Subtotals                       | 44290(-1010) | 264(+0) | 205518(+0) |
Total Static RAM memory (data + bss): 205782(+0) bytes
Total Flash memory (text + data): 44554(-1010) bytes

@mprse
Copy link
Contributor Author

mprse commented Sep 10, 2019

@kjbracey-arm Can you please review.


constexpr PinMap get_analogin_pinmap_t(const PinName pin)
{
for (uint32_t i = 0; i < (sizeof(PINMAP_ANALOGIN)/sizeof(PINMAP_ANALOGIN[0])); i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we're in C++14 land, this can be

for (const PinMap &pinmap: PINMAP_ANALOGIN) {
    if (pinmap.pin == pin) {
         return pinmap;
    }
}

Or you could go nuts and do this:

PinMap *pinmap= std::find_if(std::begin(PINMAP_ANALOGIN), std::end(PINMAP_ANALOGIN), 
                              [&](const PinMap &e) { return e.pin == pin });
if (pinmap) {
    return *pinmap;
}

But that latter more-verbose form only makes sense if you were really thinking PINMAP_ANALOGIN might be some sort of container which wanted to do a faster-than-linear search for performance reasons, so forget I even mentioned it...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, but only for peripherals with one pin. It seems that when more pin tables need to be seach and the results compared the current version is better.

@@ -23,6 +23,8 @@
#include "analogout_api.h"
#include "i2c_api.h"
#include "serial_api.h"
#define CONSTEXPR constexpr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is local trickery, I'd be inclined to call it PINMAP_CONSTEXPR or something, to make sure not to collide with someone else doing something similar.

Alternatively, maybe you could incorporate something like this into <mstd_cstddef>? There are some "C++11 or C++14" macros there - maybe we could have "C++11 or C99/C++98" macros to go with them? Make that header work in C mode, and define MSTD_CONSTEXPR_OBJ as const or constexpr accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, I don't think you technically need to actually mark those tables as constexpr anyway. As long as they are constant expressions, these function definitions should work and be constexpr.

Putting the constexpr in the table just forces a pre-check - you'd get a compile error from the tables themselves if you put something non-constant in.

I guess it's kind of nice to force the check - with just const there is a chance the table silently goes into RAM if not a constant expression, but constexpr blocks it. But we've managed without that check up until now, so I wouldn't sweat it too much.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked that and it looks like we need constexpr for the pinmap tables:

Compile [ 99.8%]: explicit_pinmap.cpp
[Error] explicit_pinmap.h@22,18: constexpr function never produces a constant expression [-Winvalid-constexpr]
[ERROR] In file included from .\hal\explicit_pinmap.cpp:27:
./hal/explicit_pinmap.h:22:18: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
constexpr PinMap get_pwm_pinmap_t(const PinName pin)
                 ^
./hal/explicit_pinmap.h:25:13: note: read of non-constexpr variable 'PinMap_PWM' is not allowed in a constant expression
        if (PINMAP_PWM[i].pin == pin) {
            ^
./targets/TARGET_Freescale/TARGET_MCUXpresso_MCUS/TARGET_MCU_K64F/TARGET_FRDM\PeripheralPinMaps.h:373:20: note: expanded from macro 'PINMAP_PWM'
#define PINMAP_PWM PinMap_PWM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. GCC does accept it, but clang refuses, and it's allowed to.

Only const ints or enumerations can be used in constant expressions - you can't actually access an array of ints. That's one of those very early C++ things where they treated const int specially to try to get people to stop using #defines.

Reference: https://stackoverflow.com/questions/45593044/the-value-of-a-const-variable-is-or-is-not-usable-in-a-constant-expression-depe

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was misled by the answer here: https://stackoverflow.com/questions/14116003/difference-between-constexpr-and-const

because I missed the clause "of integral or enumeration type" in the bit about "an object can be used as constant expression without being declared constexpr".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Used MSTD_CONSTEXPR_OBJ_C14 macro.

return {PINMAP_UART_TX[tx_idx].peripheral, PINMAP_UART_TX[tx_idx].pin, PINMAP_UART_TX[tx_idx].function, PINMAP_UART_RX[rx_idx].pin, PINMAP_UART_RX[rx_idx].function};
}

constexpr serial_fc_pinmap_t get_uart_fc_pinmap_t(const PinName rxflow, const PinName txflow)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still think these functions shouldn't have the _t suffix. You're getting the pinmap, which has type pinmap_t. Only types should have the _t suffix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

#define CONSTEXPR constexpr
#include "PeripheralPinMaps.h"

constexpr PinMap get_pwm_pinmap_t(const PinName pin)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions will need to use the MSTD_CONSTEXPR_FN_14 macro from <mstd_cstddef> to not fall over with ARMC5. ARMC5 doesn't support C++14 constexpr, so these functions would not in fact be constexpr for it, and would fail to compile.

Obviously then these functions would not save ROM on ARMC5, as they'd pull in the runtime tables, but that just needs to be documented. Can't break compilation altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed as suggested.


{NC , NC , 0}
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing newline

#endif

/************GPIO***************/
MSTD_CONSTEXPR_OBJ_14 PinMap PinMap_GPIO[] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is sort of the right idea, but it's misusing the macro a bit. The 14 macros are intended to be "this is constexpr if C++14 or later". This stuff is "constexpr if C++11 or later". (Even if the mapping function isn't constexpr until C++14, these tables can be constexpr in C++11).

You could define that yourself as

#if __cplusplus >= 201103 // I think that's the right number
#define MSTD_CONSTEXPR_OBJ_11 constexpr
#else
#define MSTD_CONSTEXPR_OBJ_11 const
#endif

but I would rather put that in mstd_cstddef itself (and adjust it to work from C). I think you won't be the only person wanting to do this in a C-or-C++ header, so let's add it to the infrastructure. (And the FN version, although that would need a little more care if people used it from C).

I didn't put this in originally because I was trying to minimise magic macros, and I was assuming all C++ would be C++11 or later, so just constexpr would do, but I didn't consider shared C-and-C++ headers.


const PinMap explicit_pinmap = {pin, peripheral, 0};

analogin_init_direct(obj, &explicit_pinmap);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use a C99 compound literal here:

analogin_init_direct(obj, &(const PinMap) { pin, peripheral, 0 });

Question of taste.


// STDIO for console print
#ifdef MBED_CONF_TARGET_STDIO_UART_TX
# define STDIO_UART_TX MBED_CONF_TARGET_STDIO_UART_TX
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not objecting to the change, but wondering why if there's a particular reason these are becoming macros? Just to line up with the FUNC macros?

Copy link
Contributor Author

@mprse mprse Sep 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I'm using macros to select how the console should be initialized. If all data required for explicit pin-map is defined, then we use explicit pin-map to init console:

# if defined(STDIO_UART_TX) && defined(STDIO_UART_TX_FUNC) && defined(STDIO_UART_RX) && defined(STDIO_UART_RX_FUNC) && defined(STDIO_UART)

But now we could use constexpr utility functions to do the job and remove these FUNC macros.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No #define STDIO_UART_TX_FUNC in case of MBED_CONF_TARGET_STDIO_UART_TX ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MBED_CONF_TARGET_STDIO_UART_XX comes from the config file, so in such case, we would have to extend the config file. I think that these extra macros can be removed now and we can use constexpr utility functions to find mappings. I'm checking this now.

Copy link
Contributor

@kjbracey kjbracey Sep 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all data required for explicit pin-map is defined, then we use explicit pin-map to init console:

Gotcha. The inability to test for enums (plus people using enums) drives me nuts in HAL-type code.

I don't particularly fancy the maintenance burden of maintaining the pinmap tables AND those separate defines. I think I'd kind of like to just pressure people to do the full constexpr thing - "if you have the constexpr stuff, retarget will use it, so your image will get smaller".

I did a parallel define thing for the us_ticker optimisations, and that's also ugly, but there is no "plan B" alternative yet... (Actually "plan A" - the defines are "plan B").

If those defines do stay, I'd want to, if possible, have a cross-check that they're correct. Have a Greentea test that checks the run-time lookup for the pins matches any declared defines. That was done for the us_ticker optimisation - defines vs us_ticker_info structure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I covered this in passing in a comment I made an hour ago. The second form prompts the compiler to generate code to put the (precomputed) spi_pinmap_t on the stack and passes a stack pointer to the function. The first form just has the precomputed pinmap in ROM and points to it.

So the first form can generate smaller code. Not sure how IAR makes it bigger though.

It may be a technical requirement of the C++ standard that it goes onto the stack - distinct objects may have to have distinct addresses, so it can't just make all calls with that pinmap point to the same thing.

(String literals can share addresses, as can C99 constant compound literals IIRC, but C++ constant temporaries can't. I think).

Copy link
Contributor

@kjbracey kjbracey Sep 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, but you'd want to make sure that separate spi_pinmap_t was static too, or it might put it on the stack anyway, if that's inside a function.

(Although in turn, if get_spi_pinmap wasn't constexpr, then static would cost you static RAM, as it would assemble it into static RAM on first call, rather than assembling it onto the stack on each call. Probably a RAM/ROM tradeoff there. But if get_spi_pinmap is constexpr, then static guarantees accessing it in ROM, rather than a stack copy, which almost certainly saves ROM and doesn't affect static RAM).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    static const serial_pinmap_t console_pinmap = get_uart_pinmap(STDIO_UART_TX, STDIO_UART_RX);
    static DirectSerial console(console_pinmap, MBED_CONF_PLATFORM_STDIO_BAUD_RATE);

Adding static gives:

  • ARM: -22 B
  • GCC_ARM: +0 B
  • IAR: -16 B

Copy link
Contributor Author

@mprse mprse Sep 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a problem while working on the serial console initialization using constexpr pin-map function. The solution works fine, but I lost backward compatibility.
At the moment console is initialized using explicit pin-map mechanism (without constexpr) if all required defines are available: STDIO_UART_TX, STDIO_UART_TX_FUNC, STDIO_UART_RX, STDIO_UART_RX_FUNC, STDIO_UART. Otherwise, we use the old version which uses pin-map tables.

Now when I'm using contsexpr function to find mapping I need to have peripheralPinMaps.h files ready for all targets.
Maybe a good solution would be to add some configuration macro for targets which support explicit pin-maps in targets.json. Something like: device_has: "EXPLICIT_PINMAP" ?
This could be removed when explicit pin-map support is added to all targets, but for now, I could add support in parts.

Copy link
Contributor

@kjbracey kjbracey Sep 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put a indicator macro in PinNames.h that you have the extended HAL functionality. That's similar to what I did in #10609 (that was new macros in device.h). Don't really need to drag the JSON into it, as it's an extension to something you can already find the main header for.

int tx_idx = NC;
int rx_idx = NC;

for (uint32_t i = 0; i < (sizeof(PINMAP_UART_TX)/sizeof(PINMAP_UART_TX[0])); i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you wanted to extend these to the ranged for, it would be

const PinMap *tx_map = nullptr;
for (const PinMap &pinmap : PINMAP_UART_TX) {
    if (pinmap.pin == tx) {
        tx_map = &pinmap;
        break;
    }
}

But at that point, the std::find_if version is actually more concise:

const PinMap *tx_map = std::find_if(std::begin(PINMAP_UART_TX), std::end(PINMAP_UART_TX),
                                    [tx](const PinMap &map) { return map.pin == tx; });
const PinMap *rx_map = std::find_if(std::begin(PINMAP_UART_RX), std::end(PINMAP_UART_RX),
                                    [rx](const PinMap &map) { return map.pin == rx; });

if (!tx_map || !rx_map || tx_map->peripheral != rx_map->peripheral) {
    return {NC, NC, NC, NC, NC};
}

return {pinmap_tx->peripheral, pinmap_tx->pin, pinmap_tx->function, pinmap_rx->pin, pinmap_tx->function};

Still a bit clunky, so you could factor it out to a helper:

template<size_t N>
constexpr const PinMap *pinmap_lookup_pin(const PinMap(&maptable)[N], PinName pin)
{
    // or use the range-loop here if you prefer. Important point was passing
    // the array by reference to capture its size for the iteration.
    return std::find_if(std::begin(maptable), std::end(maptable),
                        [pin](const PinMap &map) { return map.pin == pin; });
}

const PinMap *tx_map = pinmap_lookup_pin(PINMAP_UART_TX, tx);
const PinMap *rx_map = pinmap_lookup_pin(PINMAP_UART_RX, rx);

Copy link
Contributor

@kjbracey kjbracey Sep 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I didn't realise that std::find_if isn't constexpr in C++14 - you'll have to use the ranged-for loop. The helper function would still make it neater though.

template<size_t N>  
constexpr const PinMap *pinmap_lookup_pin(const PinMap(&maptable)[N], PinName pin)
{
    for (const PinMap &pinmap : maptable) {
       if (pinmap.pin == tx) {
            return &pinmap;
        }
    }
    return nullptr;
}

@kjbracey
Copy link
Contributor

kjbracey commented Sep 11, 2019

Just been experimenting a little in Compiler Explorer to see how much the separate pinmap object is needed with the constexpr - comparing

constexpr spi_pinmap_t spimap = get_spi_pinmap(D1, D2);
SPI myspi(spimap);

with

class SPI {
    SPI(PinName rx, PinName tx) : SPI(get_spi_pinmap(rx, tx)) { }
    SPI(const spi_pinmap_t &map);
};
SPI myspi(D1, D2);

Turns out the compilers can eliminate the runtime pinmap tables in the second form, but will only do so under certain optimisation settings. GCC seems to do it at O1, O2 or Os, but not O0. Clang seems to do it at O2, but not at O0, O1, Oz or Os. It appears its "size" optimisation is eliminating enough inlining that the tables get pulled in, so Os is bigger than O2. (I tried an always_inline attribute, but didn't affect it).

Also, the second form in both gcc and clang seems to insist on putting the temporary spi_pinmap_t on the stack, which means more code than just referencing a pre-built one in ROM.

So the separate pinmap object form seems preferable to guarantee optimisation and make it smaller, but we could end up optimising existing users anyway, in some tools/profiles. (If you were to reorganise the constructors along those lines, so the pin ones are in the headers for inlining and delegate to the map ones).

@kjbracey
Copy link
Contributor

kjbracey commented Sep 11, 2019

Further thought - not sure if it makes sense in terms of sequencing of changes/implementation, but

const spi_pinmap_t spimap = get_spi_pinmap(D1, D2);
SPI myspi(spimap);

This code - with const instead of constexpr on the spimap - could be a portable form. It allows for get_spi_pinmap to be compile time or not. If get_spi_pinmap is constexpr, then that eliminates the tables and spimap is constant-initialized. If it's not, then spimap is dynamic initialised via tables. But the same code works either way. spimap itself doesn't need to be constexpr as you're not passing it to a constexpr constructor.

I guess that does waste a bit of RAM if get_spi_pinmap isn't constexpr though - it will reserve RAM space for spimap which doesn't really need to persist after construction of myspi.

@mprse
Copy link
Contributor Author

mprse commented Sep 13, 2019

I think I addressed all the review comments.

Last changes:

  • mstd_cstddef: added C support and macros for C++11,
  • Added constexpr pin-map utility functions support for NUCLEO_F429ZI,
  • Updated implementation of constexpr pin-map utility functions,
  • Used constexpr pin-map utility functions to init serial console (backward compatibility preserved),
  • Adapted FPGA tests to test xxx_init_direct() functions and constexpr pin-map utility functions.

Test results:

| target     | platform_name | test suite                                  | result | elapsed_time (sec) | copy_method |
|------------|---------------|---------------------------------------------|--------|--------------------|-------------|
| K64F-ARMC6 | K64F          | tests-mbed_hal_fpga_ci_test_shield-analogin | OK     | 19.07              | default     |
| K64F-ARMC6 | K64F          | tests-mbed_hal_fpga_ci_test_shield-gpio     | OK     | 23.95              | default     |
| K64F-ARMC6 | K64F          | tests-mbed_hal_fpga_ci_test_shield-gpio_irq | OK     | 23.0               | default     |
| K64F-ARMC6 | K64F          | tests-mbed_hal_fpga_ci_test_shield-i2c      | OK     | 23.11              | default     |
| K64F-ARMC6 | K64F          | tests-mbed_hal_fpga_ci_test_shield-pwm      | OK     | 52.86              | default     |
| K64F-ARMC6 | K64F          | tests-mbed_hal_fpga_ci_test_shield-spi      | OK     | 22.64              | default     |
| K64F-ARMC6 | K64F          | tests-mbed_hal_fpga_ci_test_shield-uart     | OK     | 29.28              | default     |


| target              | platform_name | test suite                                  | result | elapsed_time (sec) | copy_method |
|---------------------|---------------|---------------------------------------------|--------|--------------------|-------------|
| NUCLEO_F429ZI-ARMC6 | NUCLEO_F429ZI | tests-mbed_hal_fpga_ci_test_shield-analogin | OK     | 18.99              | default     |
| NUCLEO_F429ZI-ARMC6 | NUCLEO_F429ZI | tests-mbed_hal_fpga_ci_test_shield-gpio     | OK     | 21.08              | default     |
| NUCLEO_F429ZI-ARMC6 | NUCLEO_F429ZI | tests-mbed_hal_fpga_ci_test_shield-gpio_irq | OK     | 20.15              | default     |
| NUCLEO_F429ZI-ARMC6 | NUCLEO_F429ZI | tests-mbed_hal_fpga_ci_test_shield-i2c      | OK     | 19.87              | default     |
| NUCLEO_F429ZI-ARMC6 | NUCLEO_F429ZI | tests-mbed_hal_fpga_ci_test_shield-pwm      | OK     | 49.44              | default     |
| NUCLEO_F429ZI-ARMC6 | NUCLEO_F429ZI | tests-mbed_hal_fpga_ci_test_shield-spi      | OK     | 23.98              | default     |
| NUCLEO_F429ZI-ARMC6 | NUCLEO_F429ZI | tests-mbed_hal_fpga_ci_test_shield-uart     | OK     | 22.54              | default     |

@kjbracey-arm Its ready for the next review round. Thank you for the valuable tips and help with the implementation.

If this solution is accepted I will run astyle for all modified files and prepare the design doc.

@mprse mprse force-pushed the explicit_pinmap_gold branch from 48ad43a to 22970fc Compare September 13, 2019 07:01
#include <mstd_cstddef>

/************GPIO***************/
MSTD_CONSTEXPR_OBJ_11 PinMap PinMap_GPIO[] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're potentially hitting a problem with multiple tables ending up in ROM in pathological cases, but I'm not sure we can do anything about that in C++14.

If you had two source files each doing

get_spi_pinmap(pin_a, pin_b);

Where pin_a and pin_b are variables, then each of those files would have an inline get_spi_pinmap which accessed a local PinMap_SPI. (const objects have internal linkage, so each person including this header gets their own local copy).

Adding extern here wouldn't actually work, as it would then end up with multiple definitions.

What we actually need is inline, but that's C++17 for objects. Maybe time for a MSTD_INLINE_OBJ_17...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may happen only for ARMC5?

Copy link
Contributor

@kjbracey kjbracey Sep 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be the issue for all compilers. We're safe as long as get_spi_pinmap is always called with constant parameters.

Fixing it properly requires compilers in C++17 mode so we can add inline to the objects.

The upshot is that it would not be safe to define SPI(PinName, PinName, ...) as an inline thing that calls get_spi_pinmap, and we should document that get_xxx_pinmap should only be used as a memory optimisation with constant parameters, otherwise the xxx(PinName...) forms should be used.

@mprse mprse force-pushed the explicit_pinmap_gold branch from 22970fc to 3b665a5 Compare September 13, 2019 09:38
@mprse
Copy link
Contributor Author

mprse commented Oct 4, 2019

Rebased this PR on top of feature-hal-spec-explicit-pinmap. @adbridge If we got CI slot we can run CI for this one to check if rebase somehow solved the problem with [DEBUG] Errors: Internal fault: [0xb3b91b:6110004].

If not we need to wait for the update of ARM compiler to 6.12

@adbridge
Copy link
Contributor

adbridge commented Oct 4, 2019

Ci started on this to check if the rebase has fixed the issues

@mbed-ci
Copy link

mbed-ci commented Oct 4, 2019

Test run: SUCCESS

Summary: 12 of 12 test jobs passed
Build number : 6
Build artifacts

@mprse
Copy link
Contributor Author

mprse commented Oct 7, 2019

@jamesbeyond @adbridge @0xc0170 Can we merge this into the feature branch?

This one is big enough. I'll continue working on this feature here: PR #11632. So potential change requests can be addressed there.

@adbridge adbridge added ready for merge and removed needs: CI release-version: 6.0.0-alpha-1 First pre-release version of 6.0.0 labels Oct 7, 2019
@adbridge adbridge merged commit 30450de into ARMmbed:feature-hal-spec-explicit-pinmap Oct 7, 2019
@kjbracey kjbracey mentioned this pull request Oct 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants